package prefix.platform.loan;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import prefix.platform.Constants;
import prefix.util.DateUtil;

public class AmortizationTable {

    /**
     * IMPORTANT - DO NOT CHANGE THIS CODE
     */

    public static List<Installment> getRemainingInstallments(double P, double apr, double monthly, Date startDate) {
        ArrayList<Installment> ins = new ArrayList<Installment>();

        double totalPrincipalPaid = 0.0;
        double totalInterestPaid = 0.0;
        double totalAmountPaid = 0.0;

        if (P < Constants.SENSITIVITY_ERROR)
            return ins;
        int j = 1;
        while (j < 100) {

            double principalBalanceBefore = getPrincipalBalance(j - 1, P, apr, monthly);
            double intrestAccured = (apr / 12.0) * principalBalanceBefore;
            double payment = monthly;
            if (payment > principalBalanceBefore + intrestAccured) {
                payment = principalBalanceBefore + intrestAccured;
            }
            double principalPymt = payment - intrestAccured;
            double pricipalBalanceAfter = principalBalanceBefore - principalPymt;

            totalInterestPaid += intrestAccured;
            totalPrincipalPaid += principalPymt;
            totalAmountPaid += payment;

            Date dueDate = DateUtil.normalizeDay(DateUtil.addMonths(startDate, (j - 1)));
            Installment in = new Installment(j, principalBalanceBefore, pricipalBalanceAfter, payment, principalPymt,
                    totalPrincipalPaid, totalAmountPaid, intrestAccured, totalInterestPaid, dueDate);
            ins.add(in);
            j++;

            if (pricipalBalanceAfter < Constants.SENSITIVITY_ERROR)
                break;
        }

        return ins;
    }

    public static List<Installment> getInstallments(double P, double apr, int mat, Date startDate, int princPaymentFreq) {
        if (princPaymentFreq > mat) {
            princPaymentFreq = mat;
        }
        if (princPaymentFreq <= 1) {
            return getAmortizationInstallments(P, apr, mat, startDate);
        } else {
            return getNonAmoritizationInstallments(P, apr, mat, startDate, princPaymentFreq);
        }
    }

    private static List<Installment> getAmortizationInstallments(double P, double apr, int mat, Date startDate) {
        ArrayList<Installment> ins = new ArrayList<Installment>();

        int N = mat;
        double monthly = getMonthlyPayment(P, mat, apr, 0); // calculate Monthly
                                                            // paymt

        double totalPrincipalPaid = 0.0;
        double totalInterestPaid = 0.0;
        double totalAmountPaid = 0.0;

        for (int j = 1; j <= N; j++) {

            double principalBalanceBefore = getPrincipalBalance(j - 1, P, apr, monthly);
            double pricipalBalanceAfter = getPrincipalBalance(j, P, apr, monthly);
            double intrestAccured = (apr / 12.0) * principalBalanceBefore;
            double principalPymt = monthly - intrestAccured;

            totalInterestPaid += intrestAccured;
            totalPrincipalPaid += principalPymt;
            totalAmountPaid += monthly;

            Date dueDate = DateUtil.normalizeDay(DateUtil.addMonths(startDate, (j - 1)));

            Installment in = new Installment(j, principalBalanceBefore, pricipalBalanceAfter, monthly, principalPymt,
                    totalPrincipalPaid, totalAmountPaid, intrestAccured, totalInterestPaid, dueDate);
            ins.add(in);
        }

        return ins;
    }

    private static List<Installment> getNonAmoritizationInstallments(double P, double apr, int mat, Date startDate,
            int princPaymentFreq) {
        ArrayList<Installment> ins = new ArrayList<Installment>();
        if (princPaymentFreq > 0 & mat % princPaymentFreq != 0) {
            return ins;
        }
        int N = mat;
        int payTimes = N / princPaymentFreq;

        double monthly = 0.0; // Monthly payment=monthly interest + principle
        double totalPrincipalPaid = 0.0;
        double totalInterestPaid = 0.0;
        double totalAmountPaid = 0.0;

        for (int j = 1; j <= N; j++) {

            double principalBalanceBefore = getNonAmortizationTablePrincipalBalance(P, j - 1, payTimes,
                    princPaymentFreq);
            double pricipalBalanceAfter = getNonAmortizationTablePrincipalBalance(P, j, payTimes, princPaymentFreq);
            double intrestAccured = (apr / 12.0) * principalBalanceBefore;
            double principalPymt = 0d;
            if (j % princPaymentFreq == 0) {
                principalPymt = P / payTimes;
            }
            monthly = principalPymt + intrestAccured;

            totalInterestPaid += intrestAccured;
            totalPrincipalPaid += principalPymt;
            totalAmountPaid += monthly;

            Date dueDate = DateUtil.normalizeDay(DateUtil.addMonths(startDate, (j - 1)));

            Installment in = new Installment(j, principalBalanceBefore, pricipalBalanceAfter, monthly, principalPymt,
                    totalPrincipalPaid, totalAmountPaid, intrestAccured, totalInterestPaid, dueDate);
            ins.add(in);
        }

        return ins;
    }

    //
    // public static double getAPR(double p, int n, double intR, double fees) {
    //
    // APRcalculator aprCalc = new APRcalculator();
    //
    // // System.out.println("\n p " + p + " n," + n + " intR, "+ intR +
    // // " fees " + fees);
    //
    // aprCalc.setDetails(p - fees, n, intR, fees);
    // EquationSolver es = new EquationSolver(aprCalc);
    //
    // double x = -1;
    // try {
    // x = es.bisection(0.00000001);
    // } catch (Exception e) {
    // e.printStackTrace();
    // }
    // double apr = x * 12;
    // return apr;
    // }
    //
    // @SuppressWarnings("serial")
    // protected static class APRcalculator implements Function {
    //
    // private double m_C; // Loan Amount
    //
    // private int m_N; // Maturity
    //
    // private double m_intR; // intrest rate
    //
    // private double m_fees; // fees
    //
    // // private int m_paymentFreq;
    //
    // public void setDetails(double requestedAmt, int maturity, double rate,
    // double fees) {
    // this.m_C = requestedAmt;
    // this.m_N = maturity;
    // this.m_intR = rate;
    // this.m_fees = fees;
    // // this.m_paymentFreq=paymentFreq;
    // }
    //
    // public double getValueAt(double a) throws Exception {
    // double C = this.m_C;
    // int N = this.m_N;
    // double intR = this.m_intR;
    // double fees = this.m_fees;
    // // int paymentFreq=this.m_paymentFreq;
    //
    // double P = AmortizationTable.getMonthlyPayment(C + fees, N, intR, 0);
    //
    // double aan = Math.pow((1 + a), N);
    // double val = (a * aan) / (aan - 1);
    // val = val - P / (C);
    //
    // return val;
    // }
    //
    // }

    /*
     * public static double getMonthlyPayment(double P,int N,double intrate){
     * return getAmortizationMonthlyPayment(P,N,intrate); }
     */

    public static double getMonthlyPayment(double P, int N, double intrate, int paymentMethod) {
        if (paymentMethod == 0) {
            return getAmortizationMonthlyPayment(P, N, intrate);
        } else {
            return getInterestOnly(P, intrate); // get the interest only
                                                // .Interest= principle * rate
        }
    }

    private static double getAmortizationMonthlyPayment(double P, int N, double intrate) {
        double i = intrate / 12.0;

        if (i == 0.0)
            return P / N;
        double a = 1.0 + i;
        double M = (P * Math.pow(a, N) * i) / (Math.pow(a, N) - 1.0);

        return M;
    }

    private static double getInterestOnly(double P, double intrate) {
        double i = intrate / 12.0;
        if (i == 0.0)
            return 0.0;

        double MI = i * P;
        return MI;
    }

    private static double getPrincipalBalance(int k, double P, double apr, double mtlPay) {

        double R = mtlPay;

        if (apr == 0.0)
            return (P - k * R);

        double i = apr / 12;

        double a = 1.0 + i;
        double bal = (P * Math.pow(a, k)) - ((R * (Math.pow(a, k) - 1.0)) / (i));
        return bal;
    }

    public static double getTotalAmountPaid(double _principal, double _apr, int numPayments, int maturity) {
        if (numPayments > maturity)
            return getTotalAmountPaid(_principal, _apr, maturity, maturity);

        List<Installment> insts = AmortizationTable.getInstallments(_principal, _apr, numPayments, new Date(), 0);
        Installment in = insts.get(numPayments - 1);

        return in.getTotalAmountPaid();
    }

    public static String printArray(double[] w) {
        String p = "[";
        int len = w.length;
        for (int i = 0; i < len; i++)
            p += Constants.d3.format(w[i]) + " ,";
        p += "]";
        return p;
    }

    private static double getNonAmortizationTablePrincipalBalance(double p, int term, int payTimes, int payFreq) {
        if (term < 0)
            term = 0;
        return p - (p / payTimes) * Math.floor(term / payFreq);
    }

    // --------------------------------------------------------------------------------------------------------------------
    // --------------------------------------------------------------------------------------------------------------------
    public static void main(String[] args) throws FileNotFoundException, IOException {

        // System.out.println("Monthly = " +
        // AmortizationTable.getMonthlyPayment(1000, Mat.getMaturity(),
        // 0.10/12));

        double interestRate = 0.1199;
        double principal = 10000;

        List<Installment> insts = AmortizationTable.getInstallments(principal, interestRate, 3, new Date(), 0);
        // List<Installment> insts =
        // AmortizationTable.getInstallments(principal, interestRate, 18, new
        // Date());
        printInstallements(insts);
        System.out.println("Num payments: " + insts.size());
        //
        // Installment inst = insts.get(30);

        // List<Installment> remaining =
        // AmortizationTable.getRemainingInstallments(1000, interestRate, 25);
        // printInstallements(remaining);
    }

    private static void printInstallements(List<Installment> insts) {

        Iterator<Installment> iter = insts.iterator();
        System.out
                .println("Pymt Number\t\tMontly pymt\t\tBal Before\t\tBal After\t\tTo Principal\t\tTotal Prcpl\t\tTo Interest\t\tTotal Intrst");
        System.out
                .println("-----------------------------------------------------------------------------------------------------------------------------");

        while (iter.hasNext()) {
            System.out.println(iter.next().toString());
        }

        Installment in = insts.get(insts.size() - 1);

        System.out.println("Total amount paid = " + in.getTotalAmountPaid());
        System.out.println("Total interest paid = " + in.getTotalInterestPaid() + "\n\n\n");
    }

}
